﻿using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Text;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Text;
using UIOC.CodeAnalysis.Extensions;
using UIOC.CodeAnalysis.Helpers;

namespace UIOC.CodeAnalysis.Generators;

internal static class AutowiredGenerator {

    public readonly struct GeneratedInfo(GeneratedSource source) {

        public GeneratedSource GeneratedSource { get; } = source;
    }

    private static void LogInfo(string message) {
        SourceOutputHelper.LogInfo(DiagnosticSeverity.Info, $"[{nameof(AutowiredGenerator)}] {message}");
    }

    public static ImmutableArray<GeneratedInfo> GenerateAutowired(
        Compilation compilation,
        ImmutableArray<ClassDeclarationSyntax> classDeclarationSyntaxes
    ) {
        var autowiredAttributeSymbol = compilation.GetTypeByMetadataName(AttributeFullNames.Autowired);
        if (autowiredAttributeSymbol == null) {
            return ImmutableArray<GeneratedInfo>.Empty;
        }

        var pooledObjectInternalInterfaceSymbol = compilation.GetTypeByMetadataName(TypeFullNames.IPooledObjectInternal);
        if (pooledObjectInternalInterfaceSymbol == null) {
            return ImmutableArray<GeneratedInfo>.Empty;
        }

        var autowiredInternalInterfaceSymbol = compilation.GetTypeByMetadataName(TypeFullNames.IAutowiredInternal);
        if (autowiredInternalInterfaceSymbol == null) {
            return ImmutableArray<GeneratedInfo>.Empty;
        }

        LogInfo($"[Assembly({compilation.Assembly.Name})] {nameof(GenerateAutowired)} ...");

        var namespaceDeclarationSyntax = SyntaxFactory.NamespaceDeclaration(SyntaxFactory.IdentifierName(compilation.Assembly.Name));

        var uiocNamespaceSyntax = SyntaxFactory.IdentifierName($"global::{NamespaceNames.UIOC}");

        var applicationExpressionSyntax = SyntaxFactory.MemberAccessExpression(
            SyntaxKind.SimpleMemberAccessExpression,
            uiocNamespaceSyntax,
            SyntaxFactory.IdentifierName(TypeNames.Application)
        );

        var typeMapExpressionSyntax = SyntaxFactory.MemberAccessExpression(
            SyntaxKind.SimpleMemberAccessExpression,
            applicationExpressionSyntax,
            SyntaxFactory.IdentifierName(TypeNames.TypeMap)
        );

        var result = ImmutableArray.CreateBuilder<GeneratedInfo>();

        var classSymbols = classDeclarationSyntaxes
            .Select(syntax => compilation.GetSemanticModel(syntax.SyntaxTree).GetDeclaredSymbol(syntax))
            .OfType<INamedTypeSymbol>()
            .Distinct(SymbolEqualityComparer.Default)
            .Cast<INamedTypeSymbol>()
            .ToList();
        foreach (var classSymbol in classSymbols) {
            var (compilationUnitSyntax, namespaceName) = GenerateAutowiredCompilationUnit(
                classSymbol,
                autowiredAttributeSymbol,
                pooledObjectInternalInterfaceSymbol,
                autowiredInternalInterfaceSymbol
            );
            if (compilationUnitSyntax == null) {
                continue;
            }
            var outputSource = SourceText.From(compilationUnitSyntax.NormalizeWhitespace().ToFullString(), Encoding.UTF8);
            var hintName = classSymbol.GetShortName().Replace("<", "(").Replace(">", ")");
            result.Add(new GeneratedInfo(
                new GeneratedSource(
                    hintName: hintName,
                    assemblyName: compilation.Assembly.Name,
                    namespaceName: namespaceName,
                    sourceText: outputSource,
                    compilationUnitSyntax: compilationUnitSyntax,
                    generatorName: nameof(AutowiredGenerator)
                )
            ));
        }
        return result.ToImmutable();
    }

    private static (CompilationUnitSyntax CompilationUnitSyntax, string NamespaceName) GenerateAutowiredCompilationUnit(
        INamedTypeSymbol classSymbol,
        INamedTypeSymbol autowiredAttributeSymbol,
        INamedTypeSymbol pooledObjectInternalInterfaceSymbol,
        INamedTypeSymbol autowiredInternalInterfaceSymbol
    ) {
        var fieldSymbolList = new List<IFieldSymbol>();

        var memberSymbols = classSymbol.GetMembers();
        foreach (var fieldSymbol in memberSymbols
            .Where(symbol => symbol is IFieldSymbol)
            .Where(symbol => symbol.GetAttributes(autowiredAttributeSymbol).Any())
            .Cast<IFieldSymbol>()
        ) {
            var typeSymbol = fieldSymbol.Type;
            if (typeSymbol.TypeKind != TypeKind.Interface) {
                continue;
            }
            fieldSymbolList.Add(fieldSymbol);
        }

        if (fieldSymbolList.Count == 0) {
            return (null, null);
        }

        var className = classSymbol.Name;

        var modifierSyntaxKind = classSymbol.DeclaredAccessibility.ToSyntaxKind();
        var modifierSyntaxTokens = new SyntaxToken[modifierSyntaxKind == null ? 1 : 2];
        modifierSyntaxTokens[modifierSyntaxTokens.Length - 1] = SyntaxFactory.Token(SyntaxKind.PartialKeyword);
        if (modifierSyntaxKind != null) {
            modifierSyntaxTokens[0] = SyntaxFactory.Token(modifierSyntaxKind.Value);
        }

        var classDeclarationSyntax = SyntaxFactory.ClassDeclaration(className).AddModifiers(modifierSyntaxTokens)
            .AddBaseListTypes(autowiredInternalInterfaceSymbol.CreateSimpleBaseTypeSyntax());

        var constructMethodExpressionStatementSyntaxList = new List<StatementSyntax>();
        var allocateMethodExpressionStatementSyntaxList = new List<StatementSyntax>();
        var releaseMethodExpressionStatementSyntaxList = new List<StatementSyntax>();
        var disposeMethodExpressionStatementSyntaxList = new List<StatementSyntax>();
        foreach (var fieldSymbol in fieldSymbolList) {
            var fieldName = fieldSymbol.Name;
            constructMethodExpressionStatementSyntaxList.Add(
                GenerateAssignmentExpressionStatement(fieldSymbol)
            );
            allocateMethodExpressionStatementSyntaxList.Add(
                GenerateMethodExpressionStatement(fieldSymbol.Name, MemberNames.IPooledObjectInternal.Allocate, pooledObjectInternalInterfaceSymbol)
            );
            releaseMethodExpressionStatementSyntaxList.Add(
                GenerateMethodExpressionStatement(fieldSymbol.Name, MemberNames.IPooledObjectInternal.Release, pooledObjectInternalInterfaceSymbol)
            );
            disposeMethodExpressionStatementSyntaxList.Add(
                GenerateDisposeExpressionStatement(fieldSymbol.Name)
            );
            var nullExpression = SyntaxFactory.LiteralExpression(SyntaxKind.NullLiteralExpression);
            var assignmentStatement = SyntaxFactory.ExpressionStatement(SyntaxFactory.AssignmentExpression(
                SyntaxKind.SimpleAssignmentExpression,
                SyntaxFactory.IdentifierName(fieldName),
                nullExpression
            )).WithSemicolonToken(SyntaxFactory.Token(SyntaxKind.SemicolonToken));
            disposeMethodExpressionStatementSyntaxList.Add(assignmentStatement);
        }

        var constructInternalMethodName = $"Internal__Autowired_{MemberNames.IAutowiredInternal.Construct}__";
        var constructInvocationExpressionStatement = GenerateBaseInvokeExpressionStatement(constructInternalMethodName);
        constructMethodExpressionStatementSyntaxList.Insert(0, constructInvocationExpressionStatement);
        var constructOverrideMethodDeclarationSyntax = GenerateOverrideMethodDeclaration(
            constructInternalMethodName,
            constructMethodExpressionStatementSyntaxList
        );

        var allocateInternalMethodName = $"Internal__Autowired_{MemberNames.IAutowiredInternal.Allocate}__";
        var allocateInvocationExpressionStatement = GenerateBaseInvokeExpressionStatement(allocateInternalMethodName);
        allocateMethodExpressionStatementSyntaxList.Insert(0, allocateInvocationExpressionStatement);
        var allocateOverrideMethodDeclarationSyntax = GenerateOverrideMethodDeclaration(
            allocateInternalMethodName,
            allocateMethodExpressionStatementSyntaxList
        );

        var releaseInternalMethodName = $"Internal__Autowired_{MemberNames.IAutowiredInternal.Release}__";
        var releaseInvocationExpressionStatement = GenerateBaseInvokeExpressionStatement(releaseInternalMethodName);
        releaseMethodExpressionStatementSyntaxList.Add(releaseInvocationExpressionStatement);
        var releaseOverrideMethodDeclarationSyntax = GenerateOverrideMethodDeclaration(
            releaseInternalMethodName,
            releaseMethodExpressionStatementSyntaxList
        );

        var disposeInternalMethodName = $"Internal__Autowired_{MemberNames.IAutowiredInternal.Dispose}__";
        var disposeInvocationExpressionStatement = GenerateBaseInvokeExpressionStatement(disposeInternalMethodName);
        disposeMethodExpressionStatementSyntaxList.Add(disposeInvocationExpressionStatement);
        var disposeOverrideMethodDeclarationSyntax = GenerateOverrideMethodDeclaration(
            disposeInternalMethodName,
            disposeMethodExpressionStatementSyntaxList
        );

        classDeclarationSyntax = classDeclarationSyntax.AddMembers(
            constructOverrideMethodDeclarationSyntax,
            allocateOverrideMethodDeclarationSyntax,
            releaseOverrideMethodDeclarationSyntax,
            disposeOverrideMethodDeclarationSyntax
        );

        var leadingTriviaSyntax = SyntaxFactory.Comment(UIOCGenerator.Leading);
        if (classSymbol.ContainingNamespace.IsGlobalNamespace) {
            // 全局命名空间
            classDeclarationSyntax = classDeclarationSyntax.WithLeadingTrivia(leadingTriviaSyntax, SyntaxFactory.CarriageReturnLineFeed);
            return (SyntaxFactory.CompilationUnit().AddMembers(classDeclarationSyntax), null);
        } else {
            // 非全局命名空间
            var namespaceName = classSymbol.ContainingNamespace.GetFullNamespaceName();
            var namespaceDeclarationSyntax = SyntaxFactory.NamespaceDeclaration(SyntaxFactory.IdentifierName(namespaceName))
                .WithLeadingTrivia(leadingTriviaSyntax, SyntaxFactory.CarriageReturnLineFeed)
                .WithTrailingTrivia(SyntaxFactory.CarriageReturnLineFeed);
            namespaceDeclarationSyntax = namespaceDeclarationSyntax.AddMembers(classDeclarationSyntax);
            return (SyntaxFactory.CompilationUnit().AddMembers(namespaceDeclarationSyntax), namespaceName);
        }
    }

    private static ExpressionStatementSyntax GenerateDisposeExpressionStatement(string fieldName) {
        return SyntaxFactory.ExpressionStatement(SyntaxFactory.InvocationExpression(
            SyntaxFactory.MemberAccessExpression(
                SyntaxKind.SimpleMemberAccessExpression,
                SyntaxFactory.MemberAccessExpression(
                    SyntaxKind.SimpleMemberAccessExpression,
                    SyntaxFactory.IdentifierName($"global::{TypeFullNames.Application}"),
                    SyntaxFactory.IdentifierName(TypeNames.TypeMap)
                ),
                SyntaxFactory.IdentifierName(MemberNames.TypeMap.Dispose)
            ),
            SyntaxFactory.ArgumentList(SyntaxFactory.SeparatedList([SyntaxFactory.Argument(SyntaxFactory.IdentifierName(fieldName))]))
        )).WithSemicolonToken(SyntaxFactory.Token(SyntaxKind.SemicolonToken));
    }

    private static ExpressionStatementSyntax GenerateAssignmentExpressionStatement(IFieldSymbol fieldSymbol) {
        return SyntaxFactory.ExpressionStatement(SyntaxFactory.AssignmentExpression(
            SyntaxKind.SimpleAssignmentExpression,
            SyntaxFactory.IdentifierName(fieldSymbol.Name),
            SyntaxFactory.InvocationExpression(
                SyntaxFactory.MemberAccessExpression(
                    SyntaxKind.SimpleMemberAccessExpression,
                    SyntaxFactory.MemberAccessExpression(
                        SyntaxKind.SimpleMemberAccessExpression,
                        SyntaxFactory.IdentifierName($"global::{TypeFullNames.Application}"),
                        SyntaxFactory.IdentifierName(TypeNames.TypeMap)
                    ),
                    SyntaxFactory.GenericName(SyntaxFactory.Identifier(MemberNames.TypeMap.Instantiate))
                    .AddTypeArgumentListArguments(fieldSymbol.Type.CreateSimpleNameSyntax())
                )
            )
        )).WithSemicolonToken(SyntaxFactory.Token(SyntaxKind.SemicolonToken));
    }

    private static ExpressionStatementSyntax GenerateMethodExpressionStatement(
        string fieldName,
        string methodName,
        INamedTypeSymbol objectInterfaceSymbol) {

        ExpressionSyntax expressionSyntax;
        if (objectInterfaceSymbol != null) {
            var objectInterfaceNameSyntax = objectInterfaceSymbol.CreateSimpleNameSyntax();
            expressionSyntax = SyntaxFactory.ParenthesizedExpression(SyntaxFactory.BinaryExpression(
                SyntaxKind.AsExpression,
                SyntaxFactory.IdentifierName(fieldName),
                objectInterfaceNameSyntax
            ));
        } else {
            expressionSyntax = SyntaxFactory.IdentifierName(fieldName);
        }
        return SyntaxFactory.ExpressionStatement(SyntaxFactory.InvocationExpression(SyntaxFactory.MemberAccessExpression(
            SyntaxKind.SimpleMemberAccessExpression,
            expressionSyntax,
            SyntaxFactory.IdentifierName(methodName)
        ))).WithSemicolonToken(SyntaxFactory.Token(SyntaxKind.SemicolonToken));
    }

    public static ExpressionStatementSyntax GenerateBaseInvokeExpressionStatement(string methodName) {
        return SyntaxFactory.ExpressionStatement(SyntaxFactory.InvocationExpression(
            SyntaxFactory.MemberAccessExpression(
                kind: SyntaxKind.SimpleMemberAccessExpression,
                expression: SyntaxFactory.IdentifierName("base"),
                operatorToken: SyntaxFactory.Token(SyntaxKind.DotToken),
                name: SyntaxFactory.IdentifierName(methodName)
            ),
            SyntaxFactory.ArgumentList()
        ), SyntaxFactory.Token(SyntaxKind.SemicolonToken));
    }

    public static MethodDeclarationSyntax GenerateOverrideMethodDeclaration(
        string methodName,
        List<StatementSyntax> statements
    ) {
        var ifDirectiveSyntax = SyntaxFactory.IfDirectiveTrivia(
            SyntaxFactory.IdentifierName("!DISABLE_DEBUGGER_ATTRIBUTE"),
            isActive: true,
            branchTaken: true,
            conditionValue: true
        );
        var endIfDirectiveSyntax = SyntaxFactory.EndIfDirectiveTrivia(isActive: true);
        return SyntaxFactory.MethodDeclaration(
            SyntaxFactory.PredefinedType(SyntaxFactory.Token(SyntaxKind.VoidKeyword)),
            methodName
        )
        .AddModifiers(SyntaxFactory.Token(SyntaxKind.ProtectedKeyword), SyntaxFactory.Token(SyntaxKind.OverrideKeyword))
        .WithBody(SyntaxFactory.Block(statements))
        .AddAttributeLists([
            SyntaxFactory.AttributeList(SyntaxFactory.SeparatedList([
                SyntaxFactory.Attribute(SyntaxFactory.QualifiedName(
                    SyntaxFactory.IdentifierName($"global::{nameof(System)}.{nameof(System.Diagnostics)}"),
                    SyntaxFactory.IdentifierName(nameof(System.Diagnostics.DebuggerHiddenAttribute))
                )),
            ])).WithLeadingTrivia(
                SyntaxFactory.Trivia(ifDirectiveSyntax),
                SyntaxFactory.CarriageReturnLineFeed
            ).WithTrailingTrivia(
                SyntaxFactory.CarriageReturnLineFeed,
                SyntaxFactory.Trivia(endIfDirectiveSyntax)
            ),
            SyntaxFactory.AttributeList(SyntaxFactory.SeparatedList([
                SyntaxFactory.Attribute(SyntaxFactory.QualifiedName(
                    SyntaxFactory.IdentifierName($"global::{NamespaceFullNames.Attributes}"),
                    SyntaxFactory.IdentifierName(nameof(UIOC.Attributes.InternalAttribute))
                )),
            ])),
            SyntaxFactory.AttributeList(SyntaxFactory.SeparatedList([
                SyntaxFactory.Attribute(SyntaxFactory.QualifiedName(
                    SyntaxFactory.IdentifierName($"global::{nameof(System)}"),
                    SyntaxFactory.IdentifierName(nameof(System.ObsoleteAttribute))
                )),
            ])),
        ]);
    }
}
